/*
    A simple MIDI sequencer example for the Modern Device Fluxamasynth shield.

    This code is released to the public domain, with no warranties.
    Author - Love Nystrom.
*/

#include <inttypes.h>
#include <avr/pgmspace.h>

#include <FlexamySynth.h>
#include <PgmChange.h>

#define LED_PIN  13 // Visual feedback LED
//#define _DBG

FlexamySynth synt;

#define HIGH_BYTE(w)    (w >> 8)
#define prog_word       prog_uint16_t

// Simple 'sounding key' table with 12bit countdown duration.
// Limits each key to play on only one channel, no layering.
// However, the SAM2195 patches can be setup with layering.
// 12bit time makes for max 4 second note duration or pause.
//
// The table size is 254 byte, a compromise between ram use,
// ease of handling, and functionality. A playing que would
// be better on ram and functionality, at the expense of more
// complicated handling. This example is meant to be clear,
// so I kept the algorithm simple.

word keyTbl[ 127 ]; // 12 bit time (ms) and 4bit channel

#define KEY_TIME(w)  (w & 0x0FFF)
#define KEY_CHAN(w)  (w & 0xF000)
#define SHR_CHAN(w)  (w >> 12)
#define SHL_CHAN(b)  (word(b) << 12)

void startNote( byte chan, byte key, byte vel, word duration )
{
  if (keyTbl[ key ]) // if key already sounding, turn it off
    synt.noteOff( SHR_CHAN( keyTbl[ key ]), key );

  synt.noteOn( chan, key, vel );
  keyTbl[ key ] = SHL_CHAN( chan ) | KEY_TIME( duration );
  
  digitalWrite( LED_PIN, HIGH ); // visual feedback
}

long msPrev = 0;
word msElapsed = 0;

void elapseNote( byte key ) 
{
  word chancode, time;
  if (keyTbl[ key ]) 
  {
    chancode = KEY_CHAN( keyTbl[ key ]);
    time = KEY_TIME( keyTbl[ key ]);
    if (time <= msElapsed)
    {
      synt.noteOff( SHR_CHAN(chancode), key );
      keyTbl[ key ] = 0;
      digitalWrite( LED_PIN, LOW );
    } 
    else 
    {
      time -= msElapsed;
      keyTbl[ key ] = chancode | time;
    }
  }
}

void hitDrum( byte drumNr, byte vel ) 
{
    synt.noteOn( DRUM_CHAN, drumNr, vel );
    synt.noteOff( DRUM_CHAN, drumNr );
}

#define BASS_CH     1
#define STRING_CH   2

#define OCT_2   OCTAVE*2
#define OCT_3   OCTAVE*3
#define OCT_4   OCTAVE*4
#define OCT_5   OCTAVE*5

// Simple 3 word play que event, 2 12bit delta times (start/duration)

#define MK_EVENT( chan,key,vel,dton,dtoff ) \
  word( SHL_CHAN(chan)|KEY_TIME(dton) ), \
  word( (word(vel) << 8) | key ), \
  word( SHL_CHAN(chan)|KEY_TIME(dtoff) )

// Very simple bass sequence with unrolled pattern loops, 192 byte.
// It would, of course, be better if we could define patterns,
// and combine and repeat them arbitrarily, instead of repeating
// the same data again and again.
// That's left for you to do, I won't steal all your fun :)

#define PULSE    200
#define BLEN     (PULSE-10)
#define BVEL     VEL_FORTISSIMO

prog_word const sequence[] = 
{
  // pattern 1
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass 1
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_G +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_B +OCT_2, BVEL, PULSE, BLEN ),  // bass
  // pattern 1
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass 2
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_G +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_B +OCT_2, BVEL, PULSE, BLEN ),  // bass
  // pattern 1
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass 3
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_G +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_B +OCT_2, BVEL, PULSE, BLEN ),  // bass
  // pattern 1
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass 4
  MK_EVENT( BASS_CH, NOTE_E +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_G +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_B +OCT_2, BVEL, PULSE, BLEN ),  // bass

  // pattern 2
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass 5
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_A  +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_Cs +OCT_3, BVEL, PULSE, BLEN ),  // bass
  // pattern 2
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass 6
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_A  +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_Cs +OCT_3, BVEL, PULSE, BLEN ),  // bass
  // pattern 2
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass 7
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_A  +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_Cs +OCT_3, BVEL, PULSE, BLEN ),  // bass
  // pattern 2
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass 8
  MK_EVENT( BASS_CH, NOTE_Fs +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_A  +OCT_2, BVEL, PULSE, BLEN ),  // bass
  MK_EVENT( BASS_CH, NOTE_Cs +OCT_3, BVEL, PULSE, BLEN )   // bass
};
prog_word const seq_nevents = sizeof(sequence) / 6;
prog_word const seq_len = sizeof(sequence) / 2;

word seq_index;

// The sequence is in flash mem and read-only.
// We need to count down the delta time for note on
// so the next event is copied to ram.

word next_event[3];

void get_next_event() 
{
  for( byte i=0; i < 3; i++ )
    next_event[i] = sequence[ seq_index+i ];
}

byte mixLevel[2];
byte revLevel, revSend[2];
byte revProg, revTime, revFeedbk, revAlgo;
byte chorLevel, chorSend[2];
byte chorProg, chorDelay, chorFeedbk, chorRate, chorDepth;

// Program setup

void setup()
{
  seq_index = 0;
  get_next_event();
  for( byte i=0; i < 127; i++ ) keyTbl[i] = 0;
  
  synt.begin();
  synt.GS_Reset(); // Reset to GS mode
  delay( 500 );
  
  synt.setPatchBank( BANK_GM ); // Bank select for all channels except 9 (drums)
  synt.programChange( BASS_CH, GM_SynthBass1 ); // Synth bass
  synt.programChange( STRING_CH, GM_StringEnsemble1 ); // String ensemble
  synt.programChange( DRUM_CHAN, DRUMS_Std ); // Standars drum set

  mixLevel[0] = 112;
  mixLevel[1] = 56;
  
  synt.setChannelVolume( BASS_CH, mixLevel[0] );
  synt.setChannelVolume( STRING_CH, mixLevel[1] );
  synt.setChannelVolume( DRUM_CHAN, 126 );

  synt.setDrumMix( DRUM_CHAN, DR_Kick,        64, CTV_CENTER,    120, 0 );
  synt.setDrumMix( DRUM_CHAN, DR_Snare,       110, CTV_CENTER+16, 120, 0 );
  synt.setDrumMix( DRUM_CHAN, DR_ClosedHiHat, 60, CTV_CENTER-16, 80, 0 );
  synt.setDrumMix( DRUM_CHAN, DR_PedalHiHat,  120, CTV_CENTER-16, 100, 0 );
  synt.setDrumMix( DRUM_CHAN, DR_OpenHiHat,   70, CTV_CENTER-16, 90, 0 );

  // Effects and post processing
  
  revProg   = REV_HALL2;
  revTime   = 120;  // 0-127
  revFeedbk = 32;   // Only for REV_DELAY and REV_PANDELAY
  revAlgo   = 3;    // Rev algorithm 0-7
  revLevel  = 96;
  revSend[0] = 64;
  revSend[1] = 88;
  
  synt.setReverb( revProg, revTime, revFeedbk, revAlgo ); // Master reverb setting
  synt.setReverbLevel( revLevel ); // Master reverb return level
  synt.setReverbSend( BASS_CH, revSend[0] );
  synt.setReverbSend( STRING_CH, revSend[1] );

  chorProg   = CHO_FLANGER;
  chorDelay  = 0;
  chorFeedbk = 96; 
  chorRate   = 3;
  chorDepth  = 80;
  chorLevel  = 80;
  chorSend[0] = 64;
  chorSend[1] = 40;
  
  synt.setChorus( chorProg, chorDelay, chorFeedbk, chorRate, chorDepth ); // Master chorus setting
  synt.setChorusLevel( chorLevel ); // Master chorus return level
  synt.setChorusSend( BASS_CH, chorSend[0] );
  synt.setChorusSend( STRING_CH, chorSend[1] );

  synt.setClippingMode( SOFT_CLIP );

  synt.setEqualizer( EQ_BASS, EqBassCtrl( 220 ), ccByte( +32 ));
  synt.setEqualizer( EQ_LOWMID, EqMidCtrl( 880 ), ccByte( 0 ));
  synt.setEqualizer( EQ_HIGHMID, EqMidCtrl( 2200 ), ccByte( 0 ));
  synt.setEqualizer( EQ_TREBLE, EqTrebleCtrl( 8800 ), ccByte( +24 ));
  synt.setSurroundVolume( 72 );
  synt.setSurroundDelay( 96 );
  synt.surroundMonoIn( false );
  synt.postprocGeneralMidi( true );
  synt.postprocReverbChorus( true );
  
  msPrev = millis();
}

// Program loop

byte  tick = 0;
short countdown = PULSE;

void drumThing() // A computed drum rhythm
{
  if ((countdown -= msElapsed) <= 0) {
    if (!(tick & 7)) hitDrum( DR_OpenHiHat, VEL_FORTISSIMO+random( 8 ));
    else if (!(tick & 3)) hitDrum( DR_PedalHiHat, VEL_FORTISSIMO+random( 8 ));
    else hitDrum( DR_ClosedHiHat, 120+random( 8 ));
    if (!(tick & 15)) hitDrum( DR_Kick, VEL_FORTE+random( 24 ));
    ++tick; countdown = PULSE;
  }
}

void loop()
{
  word chancode, deltatime;
    
  long now = millis();
  msElapsed = word( now - msPrev );

  drumThing();
  
  // See if sounding notes delta time have expired for note off

  for( byte i=0; i < 127; i++ ) elapseNote( i );

  // See if delta time for note on has expired for next event

  deltatime = KEY_TIME( next_event[0] );
  if (deltatime <= msElapsed) 
  {
    startNote (
      SHR_CHAN( next_event[ 0 ]),   // channel
      MIDIDATA( next_event[ 1 ]),   // key
      HIGH_BYTE( next_event[ 1 ]),  // velocity
      KEY_TIME( next_event[ 2 ])    // duration
      );
    if ((seq_index += 3) >= seq_len) seq_index = 0; // repeat
    get_next_event();
  }
  else // Count down the delta time for next note on
  {
    deltatime -= msElapsed;
    chancode = KEY_CHAN( next_event[ 0 ]);
    next_event[ 0 ] = chancode | deltatime;
  }

  // Do something more if you want to

  msPrev = now;
}
